import cv2
import numpy as np
from PIL import ImageFont, ImageDraw, Image


class Frame:
    """框架控件（静态，不处理鼠标事件）"""
    def __init__(self, x, y, width, height, bd=2, bg=(100, 100, 100)):
        self.x1 = x
        self.y1 = y
        self.x2 = x + width
        self.y2 = y + height
        self.bd = bd  # 边框宽度（暂未使用，可扩展）
        self.bg = bg  # 背景颜色

    def draw(self, img_cv):
        """绘制框架背景"""
        cv2.rectangle(img_cv, (self.x1, self.y1), (self.x2, self.y2), self.bg, -1)
        return img_cv

    # Frame 不处理鼠标事件，空实现避免报错
    def handle_event(self, event, x, y):
        pass


class Button:
    """按钮类（内置事件处理逻辑）"""
    def __init__(self, x, y, width, height, text, command, font):
        # 位置和尺寸
        self.x1 = x
        self.y1 = y
        self.x2 = x + width
        self.y2 = y + height
        # 文本和回调
        self.text = text
        self.command = command
        # 状态
        self.hover = False  # 是否悬停
        self.font = font    # 字体

    def is_inside(self, x, y):
        """判断坐标是否在按钮区域内"""
        return self.x1 <= x <= self.x2 and self.y1 <= y <= self.y2

    def handle_event(self, event, x, y):
        """处理鼠标事件（封装点击和悬停逻辑）"""
        # 1. 处理悬停状态（鼠标移动时更新）
        if event == cv2.EVENT_MOUSEMOVE:
            self.hover = self.is_inside(x, y)
        
        # 2. 处理左键点击（执行回调）
        elif event == cv2.EVENT_LBUTTONDOWN:
            if self.is_inside(x, y):
                self.command()  # 执行点击回调

    def draw(self, img_cv):
        """绘制按钮（背景、边框、文字）"""
        # 1. 绘制背景和边框
        color = (200, 220, 255) if self.hover else (255, 255, 255)
        cv2.rectangle(img_cv, (self.x1, self.y1), (self.x2, self.y2), color, -1)  # 背景
        cv2.rectangle(img_cv, (self.x1, self.y1), (self.x2, self.y2), (0, 0, 0), 2)  # 边框

        # 2. 绘制中文文字
        img_pil = Image.fromarray(cv2.cvtColor(img_cv, cv2.COLOR_BGR2RGB))
        draw = ImageDraw.Draw(img_pil)
        
        bbox = self.font.getbbox(self.text)
        text_width = bbox[2] - bbox[0]
        text_height = bbox[3] - bbox[1]
        text_x = self.x1 + (self.x2 - self.x1 - text_width) // 2
        text_y = self.y1 + (self.y2 - self.y1 - text_height) // 2
        
        draw.text((text_x, text_y), self.text, font=self.font, fill=(0, 0, 0))
        
        # 转回OpenCV格式
        return cv2.cvtColor(np.array(img_pil), cv2.COLOR_RGB2BGR)

class Label:
    """标签类（显示文字或图片）"""
    def __init__(self, x, y, width, height, text="", image=None, font=None, 
                 text_color=(0, 0, 0), bg_color=None, align="center"):
        # 位置和尺寸
        self.x1 = x
        self.y1 = y
        self.x2 = x + width
        self.y2 = y + height
        self.width = width
        self.height = height
        
        # 文本属性
        self.text = text
        self.text_color = text_color
        self.font = font if font else ImageFont.load_default()
        
        # 图片属性
        self.image = self._load_image(image) if image is not None else None
        
        # 其他属性
        self.bg_color = bg_color  # 背景颜色，None为透明
        self.align = align  # 文字/图片对齐方式: left, center, right
        
    def _load_image(self, image):
        """加载图片，可以是路径或numpy数组"""
        if isinstance(image, str):
            # 如果是字符串，尝试作为路径加载
            img = cv2.imread(image)
            if img is None:
                raise ValueError(f"无法加载图片: {image}")
            return img
        elif isinstance(image, np.ndarray):
            # 如果是numpy数组，直接使用
            return image
        else:
            raise TypeError("image参数必须是图片路径或numpy数组")
    
    def set_text(self, text):
        """设置标签文字"""
        self.text = text
        
    def set_image(self, image):
        """设置标签图片"""
        self.image = self._load_image(image) if image is not None else None
    
    def draw(self, img_cv):
        """绘制标签（背景、文字、图片）"""
        # 确保不超出图像范围
        x1 = max(0, self.x1)
        y1 = max(0, self.y1)
        x2 = min(img_cv.shape[1], self.x2)
        y2 = min(img_cv.shape[0], self.y2)
        actual_width = x2 - x1
        actual_height = y2 - y1
        
        # 绘制背景（如果设置了背景色）
        if self.bg_color is not None:
            cv2.rectangle(img_cv, (x1, y1), (x2, y2), self.bg_color, -1)
        
        # 绘制图片（如果有）
        if self.image is not None and actual_width > 0 and actual_height > 0:
            # 调整图片大小以适应标签实际可用区域
            img_h, img_w = self.image.shape[:2]
            scale = min(actual_width / img_w, actual_height / img_h)
            new_w = int(img_w * scale)
            new_h = int(img_h * scale)
            resized_img = cv2.resize(self.image, (new_w, new_h))
            
            # 根据对齐方式计算位置，确保不超出边界
            if self.align == "left":
                x = x1
            elif self.align == "center":
                x = x1 + (actual_width - new_w) // 2
            else:  # right
                x = x2 - new_w
                
            y = y1 + (actual_height - new_h) // 2
            
            # 再次确保不超出图像范围
            x_end = min(x + new_w, img_cv.shape[1])
            y_end = min(y + new_h, img_cv.shape[0])
            resize_w = x_end - x
            resize_h = y_end - y
            
            # 如果需要，再次调整大小以适应剩余空间
            if resize_w != new_w or resize_h != new_h:
                resized_img = cv2.resize(resized_img, (resize_w, resize_h))
            
            # 绘制图片
            img_cv[y:y_end, x:x_end] = resized_img
        
        # 绘制文字（如果有）
        if self.text and self.font and actual_width > 0 and actual_height > 0:
            img_pil = Image.fromarray(cv2.cvtColor(img_cv, cv2.COLOR_BGR2RGB))
            draw = ImageDraw.Draw(img_pil)
            
            bbox = self.font.getbbox(self.text)
            text_width = bbox[2] - bbox[0]
            text_height = bbox[3] - bbox[1]
            
            # 根据对齐方式计算位置
            if self.align == "left":
                text_x = x1 + 5  # 左边留5px边距
            elif self.align == "center":
                text_x = x1 + (actual_width - text_width) // 2
            else:  # right
                text_x = x2 - text_width - 5  # 右边留5px边距
                
            text_y = y1 + (actual_height - text_height) // 2
            
            # 确保文字不超出边界
            text_x = max(x1, min(text_x, x2 - text_width))
            text_y = max(y1, min(text_y, y2 - text_height))
            
            draw.text((text_x, text_y), self.text, font=self.font, fill=self.text_color)
            
            # 转回OpenCV格式
            img_cv = cv2.cvtColor(np.array(img_pil), cv2.COLOR_RGB2BGR)
            
        return img_cv
    
    # Label 不处理鼠标事件
    def handle_event(self, event, x, y):
        pass

class OpenCVUI:
    """主UI类（仅负责事件分发和绘制）"""
    def __init__(self, window_name="GUI", width=800, height=600, fullscreen=False):
        self.window_name = window_name
        self.width = width
        self.height = height
        self.widgets = []  # 存储所有控件（Frame/Button）
        self.background = np.ones((height, width, 3), dtype=np.uint8) * 240
        self.running = True
        self.font = self.load_font()

        # 创建窗口并绑定鼠标事件
        cv2.namedWindow(self.window_name)
        if fullscreen:
            cv2.setWindowProperty(self.window_name, cv2.WND_PROP_FULLSCREEN, cv2.WINDOW_FULLSCREEN)
        cv2.setMouseCallback(self.window_name, self._on_mouse)

    def load_font(self, font_path="simhei.ttf", font_size=20):
        """加载中文字体"""
        try:
            return ImageFont.truetype(font_path, font_size)
        except:
            try:
                return ImageFont.truetype("C:/Windows/Fonts/simhei.ttf", font_size)
            except:
                try:
                    return ImageFont.truetype("/usr/share/fonts/wqy-zenhei/wqy-zenhei.ttc", font_size)
                except:
                    print("警告：未找到中文字体，可能无法正常显示中文！")
                    return ImageFont.load_default()

    def add_frame(self, x, y, width, height, bd=2, bg=(200, 200, 200)):
        """添加框架控件"""
        frame = Frame(x, y, width, height, bd, bg)
        self.widgets.append(frame)

    def add_button(self, x, y, width, height, text, command):
        """添加按钮控件"""
        button = Button(x, y, width, height, text, command, self.font)
        self.widgets.append(button)

    def add_label(self, x, y, width, height, text="", image=None, font=None, 
                 text_color=(0, 0, 0), bg_color=None, align="center"):
        """添加标签控件"""
        # 如果未指定字体，使用默认字体
        label_font = font if font else self.font
        label = Label(x, y, width, height, text, image, label_font, text_color, bg_color, align)
        self.widgets.append(label)
        return label  # 返回控件实例，方便后续操作

    def _draw_all(self):
        """绘制所有控件"""
        img_cv = self.background.copy()
        for widget in self.widgets:
            img_cv = widget.draw(img_cv)
        return img_cv

    def _on_mouse(self, event, x, y, flags, param):
        """事件分发：将鼠标事件传递给所有控件自行处理"""
        for widget in self.widgets:
            widget.handle_event(event, x, y)  # 控件自己决定如何处理事件

    def mainloop(self):
        """主循环"""
        while self.running:
            frame = self._draw_all()
            cv2.imshow(self.window_name, frame)
            if cv2.waitKey(10) == 27:  # ESC退出
                self.destroy()

    def destroy(self):
        """销毁窗口"""
        print("正在退出程序...")
        self.running = False
        cv2.destroyAllWindows()


# 示例用法
if __name__ == "__main__":
    import os

    # 在程序开头设置DISPLAY环境变量，等价于export DISPLAY=:0
    os.environ["DISPLAY"] = ":0"

    ui = OpenCVUI("Hand", 480, 800, fullscreen=True)
    cnt = 0

    # 回调函数
    def btn1_click():
        global cnt
        cnt += 1
        text_label.set_text("按钮被点击了！次数: {}".format(cnt))
        print("按钮1被点击！")


    def exit_click():
        print("退出程序")
        ui.destroy()

    # 获取当前脚本所在目录
    current_dir = os.path.dirname(os.path.abspath(__file__))
    image_path = os.path.join(current_dir, "apple.jpg")

    # 添加控件（框架+按钮）
    # ui.add_frame(5, 5, 470, 390)
    ui.add_frame(5, 405, 470, 300)


    image_label = ui.add_label(5, 5, 470, 390, image=image_path, align="center")
    text_label = ui.add_label(5, 500, 470, 300, text="这是一个标签", align="center")

    
    ui.add_button(180, 420, 120, 50, "按钮", btn1_click)
    

    
    
    ui.add_button(180, 740, 120, 40, "退出", exit_click)



    ui.mainloop()